#!/usr/bin/env python

import os, sys
import smtplib
import mimetypes

from os3.types.odict import odict

from email import encoders
from email.mime.audio import MIMEAudio
from email.mime.base import MIMEBase
from email.mime.image import MIMEImage
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText


class Email ( object ):
	"""
	The ``Email`` class is used to prepare and send emails with text, HTML and attachments.

	The Email constructur is as follows:

	Email ( sender, subject, text = "", html = "" )

	Where:
		- ``sender``	- The email address of the sender (mandatory)
		- ``subject``	- The subject of the email (mandatory)
		- ``text``	- The email plain text
		- ``html``	- The email HTML text

	These fields can also be changed at runtime using the attributes:

		- ``sender``   - for the sender
		- ``subject``  - for the subject
		- ``text``     - for email plain text
		- ``html``     - for email HTML text

	Destination email addresses can be added using the ``to()``, ``cc()`` and ``bcc()`` methods, while attachments
	can be added with the ``attach()`` method.

	Once ready, the email is sent using the ``send()`` method.

	To configure SMTP, use the ``set_smtp()`` method.
	
	"""
	def __init__ ( self, sender, subject, text = "", html = "", reply_to = None ):
		self.sender = sender
		self.subject = subject
		self.reply_to = reply_to

		self._text = text
		self._html = html

		self._to   = []
		self._cc   = []
		self._bcc  = []

		self._attachments = []
		self._headers = odict ()

		self._smtp = {
			'server' : 'localhost',
			'port' : 25,
			'login' : None,
			'pwd' : None
		}


	def _add_dest ( self, lst, dest ):
		if isinstance ( dest, list ):
			lst += dest
		else:
			lst.append ( dest )

	def to ( self, dest ):
		"""
		to ( dest ) -> None

		Add one or more destination email addresses to the ``TO:`` field.

		INPUT:
			- ``dest``	- It can be both a string with a single email address
					  or a list of strings.

		This method can be invoked multiple times for the same email, and all addresses will be
		appended to the current list.

		SEE ALSO:
			- ``cc()``	- To add ``CC:`` emails
			- ``bcc()``	- To add ``BCC:`` emails
		"""
		self._add_dest ( self._to, dest )

	def cc ( self, dest ):
		"""
		cc ( dest ) -> None

		Add one or more destination email addresses to the ``CC:`` field.

		INPUT:
			- ``dest``	- It can be both a string with a single email address
					  or a list of strings.

		This method can be invoked multiple times for the same email, and all addresses will be
		appended to the current list.

		SEE ALSO:
			- ``to()``	- To add ``TO:`` emails
			- ``bcc()``	- To add ``BCC:`` emails
		"""
		self._add_dest ( self._cc, dest )

	def bcc ( self, dest ):
		"""
		bcc ( dest ) -> None

		Add one or more destination email addresses to the ``BCC:`` field.

		INPUT:
			- ``dest``	- It can be both a string with a single email address
					  or a list of strings.

		This method can be invoked multiple times for the same email, and all addresses will be
		appended to the current list.

		SEE ALSO:
			- ``to()``	- To add ``TO:`` emails
			- ``cc()``	- To add ``CC:`` emails
		"""
		self._add_dest ( self._bcc, dest )

	def header ( self, name, value ):
		self._headers [ name ] = value

	def attach ( self, fname ):
		"""
		attach ( fname ) -> None

		Attach a new file to the email.

		INPUT:
			- ``fname``		The name of the file to be added

		The right MIME Type is taken from the provided ``fname``.

		Multiple attachments can be added by calling this method multiple times.
		"""

		if not os.path.isfile ( fname ): return

		def _read_bin ( path, cls, subtype ):
			fp = open ( path, "rb" )
			msg = cls ( fp.read (), _subtype = subtype )
			fp.close ()
			return msg
			
		# Guess the content type based on the file's extension.  Encoding
		# will be ignored, although we should check for simple things like
		# gzip'd or compressed files.
		ctype, encoding = mimetypes.guess_type ( fname )

		if ctype is None or encoding is not None:
			# No guess could be made, or the file is encoded (compressed), so
			# use a generic bag-of-bits type.
			ctype = 'application/octet-stream'

		maintype, subtype = ctype.split ( '/', 1 )

		if maintype == 'text':
			fp = open ( path )
			# Note: we should handle calculating the charset
			msg = MIMEText ( fp.read(), _subtype = subtype )
			fp.close()

		elif maintype == 'image':
			msg = _read_bin ( fname, MIMEImage, subtype )

		elif maintype == 'audio':
			msg = _read_bin ( fname, MIMEAudio, subtype )

		else:
			fp = open ( fname, 'rb' )
			msg = MIMEBase ( maintype, subtype )
			msg.set_payload ( fp.read() )
			fp.close()
			# Encode the payload using Base64
			encoders.encode_base64 ( msg )

		msg.add_header ( 'Content-Disposition', 'attachment', filename = os.path.basename ( fname ) )

		self._attachments.append ( msg )

	def set_smtp ( self, server = 'localhost', port = 25, login = None, pwd = None ):
		"""
		set_smtp ( server = 'localhost', port = 25, login = None, pwd = None )

		Use this method to configure your SMTP server settings.
		
		INPUT:
			- ``server``	- The server name or IP address
			- ``port``	- The server port
			- ``login``	- The login (if the SMTP server requires authentication)
			- ``pwd``	- The password (if the SMTP server requires authentication)

		SEE ALSO:
			- ``send()``   for sending email
		"""
		self._smtp [ 'server' ] = server
		self._smtp [ 'port' ] = port
		self._smtp [ 'login' ] = login
		self._smtp [ 'pwd' ] = pwd
	
	def text ( self, txt ):
		self._text = txt.encode ( "utf-8" )

	def html ( self, html ):
		self._html = html.encode ( "utf-8" )
		
	def debug ( self ):
		mail = self._prepare ()
		return { "sender" : self.sender, "dest" :  self._to + self._cc + self._bcc, "mail" : mail }

	def send ( self ):
		"""
		send ()

		This method simply sends the email once it is ready.

		SEE ALSO:
			- ``set_smtp()`` for the SMTP settings
		"""
		mail = self._prepare ()

		s = smtplib.SMTP ( host = self._smtp [ 'server' ], port = self._smtp [ 'port' ] )
		if self._smtp.get ( "login" ):
			s.login ( self._smtp [ 'login' ], self._smtp [ 'pwd' ] )

		dest = self._to + self._cc + self._bcc

		try:
			s.sendmail ( self.sender, dest, mail )
		except:
			sys.stderr.write ( "** ERROR: sending email to: %s\n" % dest )

		s.quit ()

	def _prepare ( self ):
		root = MIMEMultipart ( "related" )
		root [ 'Subject' ] = self.subject
		root [ 'From' ] = self.sender

		if self.reply_to:
			root [ 'Reply-to' ] = self.reply_to

		root [ 'To' ]   = ','.join ( self._to )
		root [ 'Cc' ]   = ','.join ( self._cc )
		root [ 'Bcc' ]  = ','.join ( self._bcc )

		for k, v in self._headers.iteritems ():
			root.add_header ( "X-" + k.upper (), v )

		msg = MIMEMultipart ( 'alternative' )
		root.attach ( msg )

		text = MIMEText ( self._text, 'plain', _charset = 'utf-8' )
		html = MIMEText ( self._html, 'html', _charset = 'utf-8' )

		msg.attach ( text )
		msg.attach ( html )

		if self._attachments:
			attachs = MIMEMultipart ( 'alt2' )

			for m in self._attachments:
				attachs.attach ( m )

			msg.attach ( attachs )

		return root.as_string ()


if __name__ == '__main__':
	m = Email ( sender = "sender@example.com <info@example.com>", subject = "Example mail" )
	m.set_smtp ( 'server_name' )
	m.to ( "dest1@example.com" )
	m.to ( "dest2@example.com" )
	m.to ( [ "dest3@example.com", "dest4@example.com" ] )

	m.cc ( "cc@example.com" )
	m.bcc ( "bcc@example.com" )

	m.text = "Plain text example"
	m.html = "HTML Text <b>example</b>"

	m.attach ( "/path/to/image" )
	m.attach ( "/path/to/audio" )

	m.header ( "fabio", "test" )
	#m.send ()
	print m.debug ()
